﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.ComponentModel;

namespace Goldlight.Mvvm.Base.Infrastructure
{
    /// <summary>
    /// This class encapsulates the transaction handling mechanism that goes with <see cref="EditableObject"/>.
    /// </summary>
    /// <typeparam name="T">Type of selector object, e.g. a ListBox.</typeparam>
    /// <typeparam name="U">Type of framework element that contains the template resources.</typeparam>
    /// <typeparam name="V">Type of content control to display.</typeparam>
    public class EditableCollectionHandler<T, U, V>
        where T : Selector
        where U : FrameworkElement
        where V : ContentControl
    {
        #region Members
        private IEditableCollectionView _iecv;
        private T _source;
        private U _frameworkElement;
        #endregion

        /// <summary>
        /// Initialize a new instance of <see cref="EditableCollectionHandler"/>.
        /// </summary>
        /// <param name="source">The selector object.</param>
        /// <param name="frameworkElement">The framework element that contains the template resources.</param>
        public EditableCollectionHandler(T source, U frameworkElement)
        {
            _source = source;
            _frameworkElement = frameworkElement;

            _iecv = CollectionViewSource.GetDefaultView(source.Items) as IEditableCollectionView;
        }

        #region Properties
        /// <summary>
        /// Get or set the name of the default template.
        /// </summary>
        public string DefaultTemplate { get; set; }
        /// <summary>
        /// Get or set the name of the edit template.
        /// </summary>
        public string EditTemplate { get; set; }
        #endregion

        /// <summary>
        /// Start an Add operation.
        /// </summary>
        public void Add()
        {
            if (!_iecv.CanAddNew)
            {
                throw new ApplicationException("You cannot start an add operation when you're inside an add or edit.");
            }
            object item = _iecv.AddNew();
            _source.UpdateLayout();

            GetDataTemplate(_iecv.CurrentAddItem);
        }

        /// <summary>
        /// Remove the currently selected item.
        /// </summary>
        public void Remove()
        {
            if (_source.SelectedItem == null)
            {
                throw new ApplicationException("You haven't selected an item to remove");
            }
            if (_iecv.IsAddingNew || _iecv.IsEditingItem)
            {
                throw new ApplicationException("Please finish adding or editing the item before attempting to add or edit a different item");
            }
            _iecv.Remove(_source.SelectedItem);
            _source.UpdateLayout();
        }

        /// <summary>
        /// Edit the currently selected item.
        /// </summary>
        public void Edit()
        {
            if (_source.SelectedItem == null)
            {
                throw new ApplicationException("You haven't selected an item to remove");
            }
            if (_iecv.IsAddingNew || _iecv.IsEditingItem)
            {
                throw new ApplicationException("Please finish adding or editing the item before attempting to add or edit a different item");
            }
            _iecv.EditItem(_source.SelectedItem);

            GetDataTemplate(_iecv.CurrentEditItem);
        }

        /// <summary>
        /// Commit any pending changes to the data source. If the application isn't in edit or add mode,
        /// nothing will be done.
        /// </summary>
        public void CommitChanges()
        {
            if (_iecv.IsEditingItem || _iecv.IsAddingNew)
            {
                V item = null;
                if (_iecv.IsAddingNew)
                {
                    item = _source.ItemContainerGenerator.ContainerFromItem(_iecv.CurrentAddItem) as V;
                    _iecv.CommitNew();
                }
                if (_iecv.IsEditingItem)
                {
                    item = _source.ItemContainerGenerator.ContainerFromItem(_iecv.CurrentEditItem) as V;
                    _iecv.CommitEdit();
                }
                // Apply the default template.
                item.ContentTemplate = GetTemplate(DefaultTemplate);
            }
        }

        /// <summary>
        /// Cancel any pending changes to the data source. If the application isn't in edit or add mode,
        /// nothing will be done.
        /// </summary>
        public void CancelChanges()
        {
            if (_iecv.IsAddingNew || _iecv.IsEditingItem)
            {
                V item = null;
                if (_iecv.IsAddingNew)
                {
                    item = _source.ItemContainerGenerator.ContainerFromItem(_iecv.CurrentAddItem) as V;
                    _iecv.CancelNew();
                }
                if (_iecv.IsEditingItem)
                {
                    item = _source.ItemContainerGenerator.ContainerFromItem(_iecv.CurrentEditItem) as V;
                    _iecv.CancelEdit();
                }
                // Apply the default template.
                item.ContentTemplate = GetTemplate(DefaultTemplate);
            }
        }

        #region Helper methods
        private void GetDataTemplate(object item)
        {
            if (string.IsNullOrEmpty(EditTemplate))
                EditTemplate = DefaultTemplate;

            if (string.IsNullOrEmpty(EditTemplate))
            {
                throw new ApplicationException("You must set up a default template and an edit template before you use ths control.");
            }
            V templateItem = _source.ItemContainerGenerator.ContainerFromItem(item) as V;
            if (templateItem != null)
            {
                templateItem.ContentTemplate = GetTemplate(EditTemplate);
            }
        }

        private DataTemplate GetTemplate(string templateName)
        {
            return _frameworkElement.FindResource(templateName) as DataTemplate;
        }
        #endregion
    }
}
